import simplejson

def dumps(obj):
    """
    Uses the simplejson dumps on an object, calling obj.__json__, if present,
    to do so.  obj.__json__ may be either a method or an attribute, but should
    return a json-serializable object (like a dictionary, list, string, or int)
    to be passed to simplejson.dumps.
    """
    if isinstance(obj, str):
        return '"' + obj.replace('"', '\\"') + '"'
    
    if hasattr(obj, '__json__'):
        if hasattr(obj.__json__, '__call__'):
            return dumps(obj.__json__())
        else:
            return dumps(obj.__json__)
    if isinstance(obj, dict):
        return '{' + ', '.join(
            [dumps(key)+':'+dumps(obj[key]) for key in obj]) + '}'
    
    if hasattr(obj, '__iter__'): #all iterables dump as lists except for strings and dicts.
        return '['+', '.join([dumps(item) for item in obj])+']'
        
    #defaut to just dumping the thing.
    #return simplejson.dumps(obj)
    raise TypeError("%s object %s is not serializable." % (
        (obj.__class__, obj)
    ))
    
class LoadPass(Exception):
    pass

def loads(string, cls=None):
    """
    Optionally takes a class as the second argument.  __loadjson__ should
    be a classmethod of cls which takes the output of simplejson.loads(string)
    and returns an instance.  __loadjson__ should raise simplerjson.LoadPass if the object is not in the correct form.
    
    Make __loadjson__ raise simplerjson.LoadPass to revert to the normal
    loading of an object.
    """
    
    if isinstance(string, str):
        loaded = simplejson.loads(string)
    else:
        loaded = string
        
    if not hasattr(cls, '__loadjson__'):
        return loaded
    
    try:
        return cls.__loadjson__(loaded)
    except LoadPass:
        print 'couldn\'t use __loadjson__ on %s' % loaded
        pass
        
    if isinstance(loaded, dict):
        return dict([
            (key, loads(loaded[key], cls)) for key in loaded
        ])
    
    if hasattr(loaded, '__iter__'):

        newlist = [loads(item, cls) for item in loaded]
        
        if newlist == list(loaded):
            #we haven't done anything, just return the original
            return loaded
        
        #convert back to a tuple if necessary
        if isinstance(loaded, tuple):
            return tuple(newlist)
        
        print 'returning newlist = %s' % newlist
        return newlist
    
    #well, nothing worked.  Just return the thing.
    return loaded
    
if __name__ == '__main__':
    class A(object):
        def __init__(self, thing1, thing2):
            self._thing1 = thing1
            self._thing2 = thing2
        
        def __json__(self):
            return {'thing1':self._thing1, 'thing2':self._thing2}
        
        @classmethod
        def __loadjson__(cls, d):
            try:
                return A(d['thing1'], d['thing2'])
            except(KeyError, TypeError):
                raise LoadPass
        
        def __repr__(self):
            return '<A _thing1="%s" _thing2="%s">' % (self._thing1, self._thing2)
        
        
    a = A('one', 'two')
    dump = dumps(a)
    print 'a = %s' % a
    print 'dump = dumps(a) = "%s"' % dump
    print 'loads(dump) = %s' % loads(dump)
    print 'loads(dump, A) = %s' % loads(dump, A)
    print 'loads([dump, 3], A) = %s' % loads([dump, 3], A)
    
    b = A('three', 'four')
    
    print
    print 'b = %s' % b
    print
    print 'dumps([a, b]) = "%s"' % dumps([a,b])
    dump = dumps({"lol":a,"narf":b})
    print
    print 'dump = dumps({"lol":a, "narf":b}) = "%s"' % dump
    print
    print 'loads(dump) = %s' % loads(dump)
    print
    print 'loads(dump, A) = %s' % loads(dump, A)
    
    
